﻿using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.InteropServices;
using System.Globalization;
using System.Diagnostics;
using System.Text;
using System.Net.Http.Headers;
using System.Web;
using System.Dynamic;
using System.Threading;
using System.ComponentModel;
using System.Xml.Serialization;
using CloudStorageLight.WpfClient.Properties;
using System.Security.Cryptography;
using System.Collections.Concurrent;
using DeltaCompressionDotNet.MsDelta;
using ViewMaker.Core.Wpf;
using ViewMaker;

namespace CloudStorageLight.WpfClient
{
    public class BlobDataManagerArgs : EventArgs
    {
        public string Path { get; set;}
        public string CheckoutUser { get; set; }
        public string Message { get; set; }
    }

    public class DownloadInfo
    {
        public string Version { get; set; }
        public DateTime? LastWriteTime { get; set; }
        public DateTime? CreationTime { get; set; }
    }

    public class UploadInfo
    {
        public string UploadedPath { get; set; }
        public DateTime? LastWriteTime { get; set; }
        public string Message { get; set; }
    }

    /// <summary>
    /// ストレージBlobのデータ操作管理
    /// </summary>
    public partial class BlobDataManager
    {
        protected const int MaxFilesSize = 10000;
        //protected const int CachedDays = 10;

        public bool Offline { get; set; }

        public static BlobDataManager Instance 
        { 
            get 
            { 
                if (_Instance == null)
                {
                    _Instance = new BlobDataManager();
                }
                return _Instance;
            }
            set
            {
                _Instance = value;
            }
        
        }
        private static BlobDataManager _Instance;

        private string serverUrl { get; set; }
        private string oDataFileItemServiceUrl { get { return serverUrl + "odata/Data"; } }
        private string oDataServiceUrl { get { return serverUrl + "odata"; } }
        private string fileServiceUrl { get { return serverUrl + "f"; } }
        private string davServiceUrl { get { return serverUrl + "d"; } }
        private CookieContainer cookies = new CookieContainer();

        private DateTime minFileDate = DateTime.FromFileTime(0);

        private List<CancellationTokenSource> ctsList = new List<CancellationTokenSource>();


        public string LoginUser { get; set; }
        public string Password { get; set; }
        [field: NonSerialized]
        public event EventHandler<BlobDataManagerArgs> ItemNotified;

        protected void OnItemNotified(BlobDataManagerArgs arg)
        {
            if (ItemNotified != null)
            {
                this.ItemNotified(this, arg);
            }
        }

        [field: NonSerialized]
        public event EventHandler<EventArgs> RecentChanged;

        protected void OnRecentChanged(EventArgs arg)
        {
            if (RecentChanged != null)
            {
                this.RecentChanged(this, arg);
            }
        }

        public BlobDataManager()
        {
        }


        /// <summary>
        /// Webサイトを開く
        /// </summary>
        /// <param name="path"></param>
        public void OpenWebSite(string path)
        {
            var url = fileServiceUrl + path;
            var spHostUrl = CloudAccountInfo["SpHostUrl"];
            if (!string.IsNullOrEmpty(spHostUrl)) url = url + "?SPHostUrl=" + HttpUtility.UrlEncode(spHostUrl) + "&";
            Process.Start(url);
        }

        /// <summary>
        /// 指定したPathのURLを取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual string GetFilePathUrl(string path)
        {
            return fileServiceUrl + path;
        }

        #region odata client
        /// <summary>
        /// 検索
        /// </summary>
        /// <param name="path"></param>
        /// <param name="query"></param>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public virtual async Task<List<TreeItem>> SearchFilesAsync(string path, string query, int start, int size)
        {
            using (var httpService = CreateHttpClient())
            {
                var urlformat = oDataFileItemServiceUrl + "/?$filter=startswith(Directory,'{0}')&$search='{1}'&$top={2}";
                var url = string.Format(urlformat, path, query, size);

                using (var response = await httpService.GetAsync(url))
                {
                    await EnsureSuccessStatusCode(response);

                    string content = await response.Content.ReadAsStringAsync();
                    var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);
                    var value = obj.value;

                    var results = new List<TreeItem>();

                    foreach (dynamic itemValue in value)
                    {
                        TreeItem item;
                        if (itemValue.Type != "Directory")
                        {
                            var fileItem = new FileItem();
                            fileItem.CheckoutUser = itemValue.CheckoutUser;
                            fileItem.SearchContent = itemValue.SearchContent;
                            fileItem.Remark = itemValue.Remark;
                            item = fileItem;
                        }
                        else
                        {
                            item = new DirectoryItem();
                        }
                        item.Id = itemValue.Id;
                        item.Type = itemValue.Type;
                        item.Path = Uri.UnescapeDataString((string)itemValue.Path);
                        item.Title = itemValue.Title;
                        item.Length = itemValue.Length;
                        item.LastModified = ConvertFileDate(itemValue.LastModified);
                        item.FileProtectedMode = itemValue.FileProtectedMode;
                        item.AttachmentFileName = GetAttachmentFileName(itemValue);

                        results.Add(item);
                    }

                    return results;


                }
            }
        }

        /// <summary>
        /// キャンセル処理
        /// </summary>
        public void Cancel()
        {
            ctsList.ForEach(x=>x.Cancel());
        }

        /// <summary>
        /// アップロード可能なファイルの条件
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public static bool CanUploadFile(string target)
        {

            try
            {
                //var info = new FileInfo(target);
                //if (info.Length == 0) return false;
                var fileAttr = File.GetAttributes(target);
                if (fileAttr == FileAttributes.Hidden) return false;
                if (System.IO.Path.GetFileName(target).StartsWith("~")) return false;
                if (Utils.IsFileLocked(target)) return false;
            }
            catch 
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// 一覧取得
        /// </summary>
        /// <param name="path"></param>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public virtual async Task<List<TreeItem>> GetFilesAsync(string path, int start, int size = MaxFilesSize)
        {
            if (!Offline)
            {
                Exception keepEx =new InvalidProgramException();
                for (int retry = 0; retry < 2; retry++)
                {
                    try
                    {
                        using (var httpService = CreateHttpClient())
                        {
                            var urlformat = oDataFileItemServiceUrl + "/?$filter=Directory%20eq%20'{0}'&$skip={1}";
                            var url = string.Format(urlformat, Uri.EscapeDataString(path.Replace("'", "''")), start);
                            if (size > 0) url += string.Format("&$top={0}", size);

                            var response = await httpService.GetAsync(url, httpService.Token);

                            //will throw an exception if not successful
                            await EnsureSuccessStatusCode(response);

                            string content = await response.Content.ReadAsStringAsync();

                            //var content = await ODataService.GetStringAsync(url);
                            var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);
                            var value = obj.value;

                            var results = new List<TreeItem>();

                            foreach (dynamic itemValue in value)
                            {
                                TreeItem item;
                                if (itemValue.Type != "Directory")
                                {
                                    var fileItem = new FileItem();
                                    fileItem.CheckoutUser = itemValue.CheckoutUser;
                                    fileItem.Remark = itemValue.Remark;
                                    item = fileItem;
                                }
                                else
                                {
                                    item = new DirectoryItem();
                                }
                                item.Id = itemValue.Id;
                                item.Type = itemValue.Type;
                                item.Path = Uri.UnescapeDataString((string)itemValue.Path);
                                item.Title = itemValue.Title;
                                item.Length = itemValue.Length;
                                item.LastModified = ConvertFileDate(itemValue.LastModified);
                                item.Owner = itemValue.Owner;
                                item.FileProtectedMode = itemValue.FileProtectedMode;
                                item.AttachmentFileName = GetAttachmentFileName(itemValue);

                                results.Add(item);
                            }

                            return results;
                        }
                    }
                    catch (Exception ex)
                    {
                        keepEx = ex;
                        if (ex.Message.IndexOf("InvalidCustomCredentials") < 0) throw;
                        var dialog = new UserPasswordVM();
                        dialog.Path = path;
                        var view = (WpfWindowView)ViewUtil.BuildView(dialog, false);
                        view.Window.Title = "login";
                        view.Window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
                        var active = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive); ;
                        if (active != null) view.Window.Owner = active;
                        view.ShowDialog();
                        if (dialog.Cancel) throw;
                    }
                }
                throw keepEx;
            }
            else
            {
                if (path == "/")
                {
                    return LoadCachedFile();

                }
                else
                {
                    return new List<TreeItem>();
                }

            }
        }

        private string GetFileName(string path)
        {
            var items = path.Split('/');
            return items.Last();
        }

        private string GetAttachmentFileName(dynamic itemValue)
        {
            if (itemValue.AttachmentFileName == null )
            {
                return GetFileName(Uri.UnescapeDataString((string)itemValue.Path));
            }
            else
            {
                return Uri.UnescapeDataString((string)itemValue.AttachmentFileName);
            }
        }

        //[Obsolete]
        //public virtual async Task<List<TreeItem>> GetAllFilesAsync(string path, int start = 0, int size = MaxFilesSize)
        //{
        //    using (var httpService = CreateHttpClient())
        //    {
        //        var urlformat = oDataFileItemServiceUrl + "/?$filter=startswith(Directory,'{0}')&$skip={1}&$top={2}";
        //        var url = string.Format(urlformat,Uri.EscapeDataString(path), start, size);

        //        var response = await httpService.GetAsync(url, httpService.Token);

        //        //will throw an exception if not successful
        //        await EnsureSuccessStatusCode(response);


        //        string content = await response.Content.ReadAsStringAsync();

        //        //var content = await ODataService.GetStringAsync(url);
        //        var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);
        //        var value = obj.value;

        //        var results = new List<TreeItem>();

        //        foreach (dynamic itemValue in value)
        //        {
        //            TreeItem item;
        //            if (itemValue.Type != "Directory")
        //            {
        //                var fileItem = new FileItem();
        //                fileItem.CheckoutUser = itemValue.CheckoutUser;
        //                fileItem.Remark = itemValue.Remark;
        //                item = fileItem;
        //            }
        //            else
        //            {
        //                item = new DirectoryItem();
        //            }
        //            item.Id = itemValue.Id;
        //            item.Type = itemValue.Type;
        //            item.Path = Uri.UnescapeDataString((string)itemValue.Path);
        //            item.Title = itemValue.Title;
        //            item.Length = itemValue.Length;
        //            item.LastModified = ConvertFileDate(itemValue.LastModified);
        //            item.Owner = itemValue.Owner;
        //            item.FileProtectedMode = itemValue.FileProtectedMode;
        //            item.AttachmentFileName = GetAttachmentFileName(itemValue);

        //            results.Add(item);
        //        }

        //        return results;

        //    }
        //}

        /// <summary>
        /// ファイル情報取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<TreeItem> GetFileAsync(string path)
        {
            using (var httpService = CreateHttpClient())
            {
                using (var response = await httpService.GetAsync(GetOdataPath(path), httpService.Token))
                {
                    if (response.StatusCode == HttpStatusCode.NotFound) return null;
                    await EnsureSuccessStatusCode(response);

                    string content = await response.Content.ReadAsStringAsync();
                    var value = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);


                    TreeItem item;
                    if (value.Type != "Directory")
                    {
                        var fileItem = new FileItem();
                        fileItem.SearchContent = value.SearchContent;
                        fileItem.CheckoutUser = value.CheckoutUser;
                        fileItem.Remark = value.Remark;
                        item = fileItem;
                    }
                    else
                    {
                        item = new DirectoryItem();
                    }
                    item.Id = value.Id;
                    item.Type = value.Type;
                    item.Path =  Uri.UnescapeDataString((string)value.Path);
                    item.Title = value.Title;
                    item.Length = value.Length;
                    item.LastModified = ConvertFileDate(value.LastModified);
                    item.FileProtectedMode = value.FileProtectedMode;
                    item.AttachmentFileName = GetAttachmentFileName(value);

                    return item;

                }
            }
        }

        /// <summary>
        /// 最新アクセスファイル取得
        /// </summary>
        /// <param name="days"></param>
        /// <returns></returns>
        public virtual async Task<List<TreeItem>> GetRecentAsync(int days=10)
        {
            using (var httpService = CreateHttpClient())
            {
                var uri = GetOdataPath(null, "GetRecent") + "(days=" + days.ToString() + ")";
                using (var response = await httpService.GetAsync(uri, httpService.Token))
                {
                    if (response.StatusCode == HttpStatusCode.NotFound) return null;
                    await EnsureSuccessStatusCode(response);
                    string content = await response.Content.ReadAsStringAsync();
                    var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);
                    var value = obj.value;

                    var results = new List<TreeItem>();

                    foreach (dynamic itemValue in value)
                    {
                        TreeItem item;
                        if (itemValue.Type != "Directory")
                        {
                            var fileItem = new FileItem();
                            fileItem.CheckoutUser = itemValue.CheckoutUser;
                            fileItem.Remark = itemValue.Remark;
                            item = fileItem;
                        }
                        else
                        {
                            item = new DirectoryItem();
                        }
                        item.Id = itemValue.Id;
                        item.Type = itemValue.Type;
                        item.Path = Uri.UnescapeDataString((string)itemValue.Path);
                        item.Title = itemValue.Title;
                        item.Length = itemValue.Length;
                        item.LastModified = ConvertFileDate(itemValue.LastModified);
                        item.Owner = itemValue.Owner;
                        item.FileProtectedMode = itemValue.FileProtectedMode;
                        item.AttachmentFileName = GetAttachmentFileName(itemValue);

                        results.Add(item);
                    }

                    return results;

                }
            }
        }

        /// <summary>
        /// バージョン履歴の取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<List<string>> GetVersion(string path)
        {
            using (var httpService = CreateHttpClient())
            {
                using (var response = await httpService.GetAsync(GetOdataPath(path, "GetVersions"), httpService.Token))
                {
                    if (response.StatusCode == HttpStatusCode.NotFound) return null;
                    await EnsureSuccessStatusCode(response);

                    string content = await response.Content.ReadAsStringAsync();
                    var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);
                    var value = obj.value;
                    var versions = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>(value.ToString());
                    var results = new List<string>();

                    foreach (var itemValue in versions)
                    {
                        results.Add(itemValue);
                    }

                    return results;
                }
            }
        }

        /// <summary>
        /// 名前の変更
        /// </summary>
        /// <param name="path"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        public virtual async Task RenameAsync(string path, string target)
        {
            using (var httpService = CreateHttpClient())
            {
                var postData = new StringContent(
                      Newtonsoft.Json.JsonConvert.SerializeObject(
                                    new { targetId = target}).ToString(),
                        Encoding.UTF8,"application/json");

                using (var response = await httpService.PostAsync(GetOdataPath(path, "Rename"), postData, httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                }
            }
        }

        /// <summary>
        /// チェックアウト
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task CheckoutAsync(string path)
        {
            using (var httpService = CreateHttpClient())
            {
                using (var response = await httpService.PostAsync(GetOdataPath(path, "Checkout"), null, httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                }
            }
        }

        /// <summary>
        /// チェックイン
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task CheckinAsync(string path)
        {
            using (var httpService = CreateHttpClient())
            {
                using (var response = await httpService.PostAsync(GetOdataPath(path, "Checkin"), null, httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                }
            }
        }

        /// <summary>
        /// アクセス権取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<string> GetAccessControlAsync(string path)
        {
            using (var httpService = CreateHttpClient())
            {
                using (var response = await httpService.GetAsync(GetOdataPath(path, "GetAccessControl"), httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                    string content = await response.Content.ReadAsStringAsync();
                    var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);
                    var value = obj.value;
                    return value;
                }
            }
        }

        /// <summary>
        /// アクセス権設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="accessTokens"></param>
        /// <returns></returns>
        public virtual async Task SetAccessControlAsync(string path, string accessTokens)
        {
            using (var httpService = CreateHttpClient())
            {
                var postData = new StringContent(
                      Newtonsoft.Json.JsonConvert.SerializeObject(
                                    new { accessTokens = accessTokens }).ToString(),
                        Encoding.UTF8, "application/json");

                using (var response = await httpService.PostAsync(GetOdataPath(path, "SetAccessControl"), postData, httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                }
            }
        }


        /// <summary>
        /// ファイルパスコード取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<string> GetFilePasscode(string path)
        {
            using (var httpService = CreateHttpClient())
            {
                using (var response = await httpService.GetAsync(GetOdataPath(path, "GetFilePasscode"), httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                    string content = await response.Content.ReadAsStringAsync();
                    var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);
                    var value = obj.value;
                    return value;
                }
            }
        }

        /// <summary>
        /// ファイルパスコード設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="passcode"></param>
        /// <returns></returns>
        public virtual async Task SetFilePasscode(string path, string passcode)
        {
            using (var httpService = CreateHttpClient())
            {
                var postData = new StringContent(
                      Newtonsoft.Json.JsonConvert.SerializeObject(
                                    new { passcode = passcode }).ToString(),
                        Encoding.UTF8, "application/json");

                using (var response = await httpService.PostAsync(GetOdataPath(path, "SetFilePasscode"), postData, httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                }
            }
        }

        /// <summary>
        /// 保護モードの設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="passcode"></param>
        /// <returns></returns>
        public virtual async Task SetProtectedMode(string path, string mode)
        {
            using (var httpService = CreateHttpClient())
            {
                var postData = new StringContent(
                      Newtonsoft.Json.JsonConvert.SerializeObject(
                                    new { mode = mode }).ToString(),
                        Encoding.UTF8, "application/json");

                using (var response = await httpService.PostAsync(GetOdataPath(path, "SetProtectedMode"), postData, httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                }
            }
        }

        /// <summary>
        /// ユーザロールの設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="usersRole"></param>
        /// <returns></returns>
        public virtual async Task SetUsersRole(string path, string usersRole)
        {
            using (var httpService = CreateHttpClient())
            {
                var postData = new StringContent(
                      Newtonsoft.Json.JsonConvert.SerializeObject(
                                    new { usersRole = usersRole }).ToString(),
                        Encoding.UTF8, "application/json");

                using (var response = await httpService.PostAsync(GetOdataPath(path, "SetUsersRole"), postData, httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                }
            }
        }

        /// <summary>
        /// バックアップモードの取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool?> GetBackupMode(string path)
        {
            using (var httpService = CreateHttpClient())
            {
                using (var response = await httpService.GetAsync(GetOdataPath(path, "GetBackupMode"), httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                    string content = await response.Content.ReadAsStringAsync();
                    var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(content);
                    var value = obj.value;
                    return value;
                }
            }
        }

        /// <summary>
        /// バックアップモード設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="backup"></param>
        /// <returns></returns>
        public virtual async Task SetBackupMode(string path, bool? backup)
        {
            using (var httpService = CreateHttpClient())
            {
                var postData = new StringContent(
                      Newtonsoft.Json.JsonConvert.SerializeObject(
                                    new { backup = backup }).ToString(),
                        Encoding.UTF8, "application/json");

                using (var response = await httpService.PostAsync(GetOdataPath(path, "SetBackupMode"), postData, httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                }
            }
        }

        /// <summary>
        /// CaseInsensitiveモード設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="caseInsensitive"></param>
        /// <returns></returns>
        public virtual async Task SetCaseInsensitive(string path, bool? caseInsensitive)
        {
            using (var httpService = CreateHttpClient())
            {
                var postData = new StringContent(
                      Newtonsoft.Json.JsonConvert.SerializeObject(
                                    new { caseInsensitive = caseInsensitive }).ToString(),
                        Encoding.UTF8, "application/json");

                using (var response = await httpService.PostAsync(GetOdataPath(path, "SetCaseInsensitive"), postData, httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                }
            }
        }

        /// <summary>
        /// 削除
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task DeleteAsync(string path)
        {
            using (var httpService = CreateHttpClient())
            {
                using (var response = await httpService.DeleteAsync(GetOdataPath(path), httpService.Token))
                {
                    await EnsureSuccessStatusCode(response);
                }
            }
        }

        /// <summary>
        /// 完全削除
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task DeleteCompletelyAsync(string path)
        {
            using (var httpService = CreateHttpClient())
            {
                var response = await httpService.PostAsync(GetOdataPath(path, "DeleteCompletely"), null, httpService.Token);
                await EnsureSuccessStatusCode(response);
            }
        }

        /// <summary>
        /// コピー
        /// </summary>
        /// <param name="path"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        public virtual async Task CopyAsync(string path, string target)
        {
            using (var httpService = CreateHttpClient())
            {
                var postData = new StringContent(
                      Newtonsoft.Json.JsonConvert.SerializeObject(
                                    new { targetId = target }).ToString(),
                        Encoding.UTF8, "application/json");

                var response = await httpService.PostAsync(GetOdataPath(path, "Copy"), postData, httpService.Token);
                await EnsureSuccessStatusCode(response);
            }
        }


        /// <summary>
        /// 移動
        /// </summary>
        /// <param name="path"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        public virtual async Task MoveAsync(string path, string target)
        {
            using (var httpService = CreateHttpClient())
            {
                var postData = new StringContent(
                      Newtonsoft.Json.JsonConvert.SerializeObject(
                                    new { targetId = target }).ToString(),
                        Encoding.UTF8, "application/json");

                var response = await httpService.PostAsync(GetOdataPath(path, "Move"), postData, httpService.Token);
                await EnsureSuccessStatusCode(response);
            }
        }

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="path"></param>
        /// <param name="target"></param>
        /// <param name="snapshot"></param>
        /// <param name="mode"></param>
        /// <param name="baseVersion"></param>
        /// <param name="progress"></param>
        /// <returns></returns>
        public virtual async Task<DownloadInfo> DownloadAsync(string path, Stream target, string snapshot = null, string mode = null, IProgress<double> progress = null, long totalSize = 0)
        {
            var info = new DownloadInfo();
            if (!Offline)
            {
                using (var httpService = CreateHttpClient())
                {
                    string ext = null;
                    if (snapshot != null) ext = "?snapshot=" + snapshot;
                    if (mode != null)
                    {
                        ext += (ext == null ? "?mode=" : "&mode=") + mode;
                    }
                    var response = await httpService.GetAsync(fileServiceUrl +  EncodeFileUrl(path) + (ext == null ? "" : ext), HttpCompletionOption.ResponseHeadersRead);
                    await EnsureSuccessStatusCode(response);

                    var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;

                    if (total == -1 && totalSize > 0) total = totalSize;
                    var canReportProgress = total != -1 && progress != null;
                    
                    using (var stream = await response.Content.ReadAsStreamAsync())
                    {
                        var totalRead = 0L;
                        var buffer = new byte[4096*16*20];
                        var isMoreToRead = true;

                        do
                        {

                            httpService.Token.ThrowIfCancellationRequested();

                            var read = await stream.ReadAsync(buffer, 0, buffer.Length, httpService.Token);

                            if (read == 0)
                            {
                                isMoreToRead = false;
                            }
                            else
                            {
                                await target.WriteAsync(buffer, 0, read);

                                totalRead += read;

                                if (canReportProgress)
                                {
                                    progress.Report((totalRead * 1d) / (total * 1d) * 100);
                                }
                            }
                        } while (isMoreToRead);
                    }
                    if (response.Headers.Any(x => x.Key == "LastWriteTime"))
                    {
                        DateTime work; 
                        var lastwriteTimeString = response.Headers.First(x => x.Key == "LastWriteTime").Value.FirstOrDefault();
                        if (DateTime.TryParse(lastwriteTimeString, out work))
                        {
                            info.LastWriteTime = work.ToUniversalTime();
                        }
                    }
                    if (response.Headers.Any(x => x.Key == "CreationTime"))
                    {
                        DateTime work;
                        var creationTimeString = response.Headers.First(x => x.Key == "CreationTime").Value.FirstOrDefault();
                        if (DateTime.TryParse(creationTimeString, out work))
                        {
                            info.CreationTime = work.ToUniversalTime();
                        }
                    }
                    if (response.Headers.Any(x => x.Key == "SnapshotTime"))
                    {
                        info.Version = response.Headers.First(x => x.Key == "SnapshotTime").Value.FirstOrDefault();
                    }
                }
            }
            else
            {
                var cacheId = path + snapshot ?? "";
                await LoadFileFromCacheAsync(target, cacheId);
            }
            return info;
        }


        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="path"></param>
        /// <param name="localPath"></param>
        /// <param name="originalFile"></param>
        /// <param name="snapshot"></param>
        /// <param name="mode"></param>
        /// <param name="baseVersion"></param>
        /// <param name="progress"></param>
        /// <returns></returns>
        public virtual async Task<DownloadInfo> DownloadAsync(string path, string localPath, string snapshot = null, string mode = null, IProgress<double> progress = null, long totalSize = 0)
        {
            DownloadInfo info = null;
            var downloadName = Path.GetTempFileName();
            var mergeTemp = Path.GetTempFileName();
            try
            {
                Directory.CreateDirectory(Path.GetDirectoryName(localPath));
                using (var st = new FileStream(downloadName, FileMode.Create))
                {
                    info = await DownloadAsync(path, st, snapshot, mode, progress, totalSize);
                }
                if (File.Exists(localPath)) ForceDeleteFile(localPath);
                File.Move(downloadName, localPath);

                if (info.LastWriteTime.HasValue) File.SetLastWriteTimeUtc(localPath, info.LastWriteTime.Value);
                if (info.CreationTime.HasValue) File.SetCreationTimeUtc(localPath, info.CreationTime.Value);

                return info;
            }
            finally
            {
                if (File.Exists(downloadName)) ForceDeleteFile(downloadName);
                if (File.Exists(mergeTemp)) ForceDeleteFile(mergeTemp);

            }
        }

        ///// <summary>
        ///// ダウンロード（Stream）
        ///// </summary>
        ///// <param name="path"></param>
        ///// <param name="snapshot"></param>
        ///// <param name="mode"></param>
        ///// <param name="baseVersion"></param>
        ///// <param name="progress"></param>
        ///// <returns></returns>
        //public virtual async Task<Stream> DownloadAsync(string path, string snapshot = null, string mode = null, string baseVersion = null, IProgress<double> progress = null)
        //{
        //    var httpService = CreateHttpClient();
        //    try
        //    {
        //        string ext = null;
        //        if (snapshot != null) ext = "?snapshot=" + snapshot;
        //        if (mode != null)
        //        {
        //            ext += (ext == null ? "?mode=" : "&mode=") + mode;
        //        }
        //        if (baseVersion != null)
        //        {
        //            ext += (ext == null ? "?baseVersion=" : "&baseVersion=") + HttpUtility.UrlEncode(baseVersion);
        //        }
        //        var response = await httpService.GetAsync(fileServiceUrl + EncodeFileUrl(path) + (ext == null ? "" : ext), HttpCompletionOption.ResponseHeadersRead);
        //        await EnsureSuccessStatusCode(response);

        //        var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;
        //        var canReportProgress = total != -1 && progress != null;

        //        return new ReadSeekableStream(await response.Content.ReadAsStreamAsync(), 1024 * 96, total, new IDisposable[] { httpService });
        //    }
        //    catch (Exception)
        //    {
        //        if (httpService != null) httpService.Dispose();
        //        throw;
        //    }
        //}










        /// <summary>
        /// フォルダ作成
        /// </summary>
        /// <param name="path"></param>
        /// <param name="newDirectory"></param>
        /// <param name="ensure"></param>
        /// <returns></returns>
        public virtual async Task CreateFolderAsync(string path, string newDirectory, bool ensure = false)
        {
            using (var httpService = CreateHttpClient())
            {
                var postData = new StringContent(
                      Newtonsoft.Json.JsonConvert.SerializeObject(
                                    new { directoryName = newDirectory , ensure = ensure}).ToString(),
                        Encoding.UTF8, "application/json");

                var response = await httpService.PostAsync(GetOdataPath(path, "CreateDirectory"), postData, httpService.Token);
                await EnsureSuccessStatusCode(response);
            }
        }

        public virtual async Task<UploadInfo> UploadFileAsync(string path, string filename, IProgress<double> progress = null)
        {
            var contentFilename = filename;
            var sendFileInfo = new FileInfo(filename);

            using (var stream = new FileStream(contentFilename, FileMode.Open, FileAccess.Read))
            {
                return await UploadFileAsync(path, filename, stream, sendFileInfo.LastWriteTimeUtc, sendFileInfo.CreationTimeUtc, progress);
            }

        }
        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="path"></param>
        /// <param name="filename"></param>
        /// <param name="originalFile"></param>
        /// <param name="snapshot"></param>
        /// <param name="progress"></param>
        /// <returns></returns>
        public virtual async Task<UploadInfo> UploadFileAsync(string path, string filename, Stream stream, DateTime lastWriteTimeUtc, DateTime creationTimeUtc,  IProgress<double> progress = null)
        {
            var info = new UploadInfo();
            using (var client = CreateHttpClient())
            {
                var buffer = new byte[50000000];
                var totalSize = stream.Length;
                bool range = buffer.Length < totalSize;
                int size=0;
                long uploadedSize = 0;
                var progressSub = new Progress<double>();
                if (progress != null)
                {
                    if (range)
                    {
                        progressSub.ProgressChanged += (s, e) =>
                        {
                            progress.Report((uploadedSize + size * e / 100) * 100 / totalSize);
                        };
                    }
                    else
                    {
                        progressSub.ProgressChanged += (s, e) =>
                        {
                            progress.Report(e);
                        };
                    }

                }

                var uploadId = Guid.NewGuid();
                size = await stream.ReadAsync(buffer, 0, buffer.Length);
                while (true)
                {
                    using (var content =
                        new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)))
                    {
                        content.Add(new StringContent(uploadId.ToString("d")), "uploadId");
                        content.Add(new ProgressableStreamContent(new MemoryStream(buffer,0,size), progressSub), "attatch", Uri.EscapeDataString(System.IO.Path.GetFileName(filename)));
                        if (range) content.Headers.Add("Content-Range", "bytes " + uploadedSize.ToString() + "-" + (uploadedSize+ size-1).ToString() + "/" + totalSize.ToString());
                        content.Headers.Add("LastWriteTime", lastWriteTimeUtc.ToString("O"));
                        content.Headers.Add("CreationTime", creationTimeUtc.ToString("O"));
                        var url = fileServiceUrl + EncodeFileUrl(path);
                        using (
                           var message =
                               await client.PostAsync(url, content, client.Token))
                        {

                            await EnsureSuccessStatusCode(message);
                            var msg = await message.Content.ReadAsStringAsync();
                            uploadedSize += size;

                            var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(msg);
                            if (!obj.status.Value) throw new ApplicationException(obj.data.Value);

                            if (message.Headers.Any(x => x.Key == "LastWriteTime"))
                            {
                                DateTime work;
                                var lastwriteTimeString = message.Headers.First(x => x.Key == "LastWriteTime").Value.FirstOrDefault();
                                if (DateTime.TryParse(lastwriteTimeString, out work))
                                {
                                    info.LastWriteTime = work.ToUniversalTime();
                                }
                            }
                            info.Message = msg;

                            if (message.Headers.Any(x => x.Key == "UploadedPath"))
                            {
                                info.UploadedPath = HttpUtility.UrlDecode(message.Headers.First(x => x.Key == "UploadedPath").Value.FirstOrDefault());
                            }
                            else
                            {
                                info.UploadedPath = path;
                            }
                        }
                    }
                    size = await stream.ReadAsync(buffer, 0, buffer.Length);
                    if (size == 0) return info;
                }

            }
        }

        public virtual async Task PostCustomAuthInfo(string path, string user, string password)
        {
            var info = new UploadInfo();
            using (var client = CreateHttpClient())
            {
                var url = fileServiceUrl + EncodeFileUrl(path);
                var userPass = new Dictionary<string, string>();
                userPass.Add("user", user);
                userPass.Add("password", password);
                var content = new FormUrlEncodedContent(userPass);

                var response = await client.PostAsync(url, content);
                await EnsureSuccessStatusCode(response);
            }
        }
        #endregion

       
        /// <summary>
        /// ログイン処理
        /// </summary>
        /// <param name="service"></param>
        /// <param name="mail"></param>
        /// <param name="pass"></param>
        /// <param name="adminRole"></param>
        /// <param name="rememberMe"></param>
        /// <returns></returns>
        public virtual async Task<bool> LoginAsync(string service, string mail, string pass, bool adminRole = false, bool rememberMe = false)
        {
            serverUrl = service;
            cookies = new CookieContainer();
            cookies.Add(new Cookie("api", "1") { Domain = new Uri(service).Host });
            LoginUser = mail;
            Password = pass;
            cloudAccountInfo = null;
            using (var client = CreateHttpClient())
            {
                var url = serverUrl + "Account/GetCloudAccount";
                if (adminRole) url = url + "?adminRole=true";
                var byteArray = Encoding.ASCII.GetBytes(LoginUser + ":" + Password);
                var header = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
                client.DefaultRequestHeaders.Authorization = header;
                var clientToken =  LoadClientToken();
                if (clientToken != null) client.DefaultRequestHeaders.Add("ClientToken", clientToken);
                try
                {
                    using (var response = await client.GetAsync(url))
                    {
                        string content = await response.Content.ReadAsStringAsync();
                        if (response.Headers.Contains("ClientToken"))
                        {
                            SaveClientToken(string.Concat(response.Headers.GetValues("ClientToken")));
                        }
                        cloudAccountInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, string>>(content);
                        if (cloudAccountInfo != null)
                        {
                            if (!string.IsNullOrEmpty(cloudAccountInfo["Account"]) && !service.Contains('('))
                            {
                                serverUrl = service + "(" + cloudAccountInfo["Account"] + ")/";
                            }
                            try
                            {
                                SaveCachedUser(serverUrl, mail, pass, rememberMe);
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine(ex);
                            }
                            Offline = false;
                            return true;
                        }
                        return false;
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                    if (TryLocalCahceLogin(serverUrl, mail, pass))
                    {
                        Offline = true;
                        return true;
                    }
                    return false;
                }

            }

        }


        private MyHttpClient CreateHttpClient()
        {
            var handler = new MyHttpClientHandler() { CookieContainer = cookies };
            var client = new MyHttpClient(handler, ctsList);
            client.Timeout = new TimeSpan(1, 0, 0);
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            return client;
        }

        class MyHttpClientHandler : HttpClientHandler
        {
            protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss fff") + "\tRequest:" + request.Method + " " + request.RequestUri.ToString());

                request.Headers.Add("Accept-Language", Thread.CurrentThread.CurrentCulture.ToString());
                System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
                System.Version ver = asm.GetName().Version;
                request.Headers.Add("Client-Version", ver.ToString());
                var status = await base.SendAsync(request, cancellationToken);

                if (status.StatusCode == HttpStatusCode.Unauthorized)
                {
                    var byteArray = Encoding.ASCII.GetBytes(BlobDataManager.Instance.LoginUser + ":" + BlobDataManager.Instance.Password);
                    var header = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
                    request.Headers.Authorization = header;
                    return await base.SendAsync(request, cancellationToken);
                }
                return status;
            }
        }
        //念のためにハンドラーをDisposeするクラスを作成して利用
        class MyHttpClient : HttpClient
        {
            private MyHttpClientHandler handler;
            private List<CancellationTokenSource> ctsList;
            private CancellationTokenSource cts;
            internal CancellationToken Token { get { return cts.Token; } }

            public MyHttpClient(MyHttpClientHandler handler, List<CancellationTokenSource> ctsList)
                : base(handler)
            {
                this.handler = handler;
                this.ctsList = ctsList;
                cts = new CancellationTokenSource();
                lock(this.ctsList)
                {
                    this.ctsList.Add(cts);
                }
            }

            protected override void Dispose(bool disposing)
            {
                base.Dispose(disposing);
                handler.Dispose();
                cts.Dispose();
                lock (this.ctsList)
                {
                    this.ctsList.Remove(cts);
                }
            }
        }

        class ProgressableStreamContent : HttpContent
        {

            private Stream content;
            private int bufferSize;
            private bool contentConsumed;

            private IProgress<double> progress;

            public ProgressableStreamContent(Stream content, IProgress<double> progress = null, int bufferSize = 4096*16)
            {
                if (content == null)
                {
                    throw new ArgumentNullException("content");
                }
                if (bufferSize <= 0)
                {
                    throw new ArgumentOutOfRangeException("bufferSize");
                }

                this.content = content;
                this.bufferSize = bufferSize;
                this.progress = progress;
            }

            protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
            {
                PrepareContent();

                return Task.Run(() =>
                {
                    var buffer = new Byte[this.bufferSize];
                    var size = content.Length;
                    long uploaded = 0;

                    using (content) while (true)
                        {
                            var length = content.Read(buffer, 0, buffer.Length);
                            if (length <= 0) break;

                            if (progress != null)
                            {
                                uploaded += length;
                                progress.Report(uploaded * 100 / content.Length);

                            }

                            stream.Write(buffer, 0, length);

                        }
                });
            }

            protected override bool TryComputeLength(out long length)
            {
                length = content.Length;
                return true;
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    content.Dispose();
                }
                base.Dispose(disposing);
            }


            private void PrepareContent()
            {
                if (contentConsumed)
                {
                    if (content.CanSeek)
                    {
                        content.Position = 0;
                    }
                    else
                    {
                        throw new InvalidOperationException();
                    }
                }

                contentConsumed = true;
            }
        }

 
#region private

        private DateTime ConvertFileDate(dynamic value)
        {
            var date = value.Value as DateTime?;
            if (!date.HasValue || date.Value < minFileDate) return minFileDate;
            return date.Value;
        }

        private string GetOdataPath(string path, string action = null)
        {
            var url = oDataFileItemServiceUrl;
            if (path != null)
            {
                url += "('" + EncodeFileUrl(path.Replace("'", "''")).Replace(".", "*").Replace("/", "|") + "')";
            }
            if (action != null)
            {
                url += "/StorageService." + action;
            }
            return url;
        }

        /// <summary>
        /// ディレクトリパスを取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string GetDirectoryPath(string path)
        {
            var pathwork = path;
            if (pathwork == "/") return null;
            if (pathwork.EndsWith("/")) pathwork = pathwork.Substring(0, pathwork.Length - 1);
            var items = pathwork.Split('/');
            return pathwork.Substring(0, pathwork.Length - items[items.Length - 1].Length);
        }


        internal static string EncodeFileUrl(string url)
        {
            return string.Join("/", url.Split('/').Select(x => Uri.EscapeDataString(x)));
        }

        public async Task EnsureSuccessStatusCode(HttpResponseMessage response)
        {
            if (!response.IsSuccessStatusCode)
            {
                var responseMessage = await response.Content.ReadAsStringAsync();
                if (string.IsNullOrEmpty(responseMessage))
                {
                    if (response.StatusCode == HttpStatusCode.NotFound) responseMessage = Resources.MsgNotFound;
                }

                if (string.IsNullOrEmpty(responseMessage)) responseMessage = response.ReasonPhrase;
                Exception ex = new Exception();
                try
                {
                    ex = new ApplicationException(Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(responseMessage).error.message.Value);
                }
                catch
                {
                    ex = new ApplicationException(responseMessage.Substring(0, Math.Min(500, responseMessage.Length)));
                }
                throw ex;
            }
        }

#endregion
    }

}
